#pragma once

#include "dx.h"
#include <atlbase.h>
#include <atlwin.h>
#include <atlstr.h>

namespace kk = KennyKerr;
using namespace std;
using namespace D2D1;

template <typename T>
struct BaseWindow :
	CWindowImpl<T, CWindow, CWinTraits<WS_OVERLAPPEDWINDOW|WS_VISIBLE, 0>>
{
	const static int WmCloseWindow = WM_USER;
	const static UINT_PTR WmTimerUpdateWindowLazy = 1;
	const static UINT_PTR WmTimerOncePerSecond = 2;
	const static int HwndUserData_This = GWLP_USERDATA;

	DECLARE_WND_CLASS_EX(nullptr, 0, -1)

	BEGIN_MSG_MAP(c)
		MESSAGE_HANDLER(WM_DESTROY, DestroyHandler)
		MESSAGE_HANDLER(WM_PAINT, PaintHandler)
		MESSAGE_HANDLER(WM_GETMINMAXINFO, GetMinMaxInfoHandler)
		MESSAGE_HANDLER(WM_SIZE, SizeHandler)
	END_MSG_MAP()

	LRESULT SizeHandler(UINT, WPARAM wparam, LPARAM, BOOL &)
	{
		if (wparam != SIZE_MINIMIZED && m_target)
		{
			m_target.SetTarget();
			m_swapChain.ResizeBuffers();
			auto surface = m_swapChain.GetBuffer();
			auto bitmap = m_target.CreateBitmapFromDxgiSurface(surface);
			m_target.SetTarget(bitmap);

			static_cast<T *>(this)->CreateDeviceSizeResources();
		}

		InvalidateRect(nullptr, false);
		return 0;
	}

	LRESULT GetMinMaxInfoHandler(UINT, WPARAM, LPARAM lparam, BOOL &)
	{
		auto info = reinterpret_cast<LPMINMAXINFO>(lparam);
		info->ptMinTrackSize.y = 100;
		return 0;
	}

	LRESULT PaintHandler(UINT, WPARAM, LPARAM, BOOL &)
	{
		Render();
		return 0;
	}

	LRESULT DestroyHandler(UINT, WPARAM, LPARAM, BOOL &)
	{
		PostQuitMessage(0);
		return 0;
	}

	void Render()
	{
		if (!m_target)
		{
			// create DeviceContext.
			auto d3d = kk::Direct3D::CreateDevice();
			auto d2d = m_2dfac.CreateDevice(d3d);
			m_target = d2d.CreateDeviceContext();

			// create swapChain.
			auto dxgi = d3d.GetDxgiFactory();
			m_swapChain = dxgi.CreateSwapChainForHwnd(d3d, m_hWnd);

			// set target.
			auto surface = m_swapChain.GetBuffer();
			auto bitmap = m_target.CreateBitmapFromDxgiSurface(surface);
			m_target.SetTarget(bitmap);

			static_cast<T *>(this)->CreateDeviceResources();
            static_cast<T *>(this)->CreateDeviceSizeResources();
		}

		m_wam.Update(GetTimeNow());
		m_target.BeginDraw();
		static_cast<T *>(this)->Draw();
		m_target.EndDraw();

		m_swapChain.Present();
		ValidateRect(nullptr);
	}

	double GetTimeNow()
	{
		double now = m_wam_timer.GetTime();
		return now;
	}

	void Create()
	{
		RECT rect = { 0, 0, 500, 500 };

		__super::Create(nullptr, rect, L"Main Window");

		// CreateDeviceIndependentResources
		m_2dfac = kk::Direct2D::CreateFactory();
		m_dwrite = kk::DirectWrite::CreateFactory();
		m_wic = kk::Wic::CreateFactory();
		m_wam = kk::Wam::CreateManager();
		m_wam_lib = kk::Wam::CreateTransitionLibrary();
		m_wam_timer = kk::Wam::CreateTimer();

		static_cast<T *>(this)->CreateDeviceIndependentResources();

		SetWindowLongPtr(HwndUserData_This, (LONG_PTR)this);
		VERIFY(InvalidateRect(nullptr, false));
	}

	// virtual functions
	void CreateDeviceIndependentResources() {}
	void CreateDeviceResources() {}
	void Draw() {}
	void CreateDeviceSizeResources() {}

	// device independent resources.
	kk::Direct2D::Factory1 m_2dfac;
	kk::DirectWrite::Factory2 m_dwrite;
	kk::Wam::Manager m_wam;
	kk::Wam::TransitionLibrary m_wam_lib;
	kk::Wam::Timer m_wam_timer;

	// device resources.
	kk::Direct2D::DeviceContext m_target;
	kk::Dxgi::SwapChain1 m_swapChain;
	kk::Wic::Factory2 m_wic;
};